#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define FILEPARSER
using namespace std;

#ifndef MPI_H
#include <mpi.h>
#endif

#ifndef MPI_HELPER
#include "mpi_helper.h"
#endif

#ifndef COOFORMAT
	struct COO_FORMAT {};
#define COOFORMAT
#endif

#ifndef DCSFORMAT
	struct DCS_FORMAT {};
	#define DCSFORMAT
#endif


class MMapFILEParserBase {
protected:
	virtual void init() {
	}

	struct stat fs;
	void * map;
	int fd;

	MMapFILEParserBase(char * filename) {
		this->fd = open(filename, O_RDONLY);

		if (fd == -1) {
			perror("Error opening file for reading");
			exit (EXIT_FAILURE);
		}

		if (fstat(fd, &fs) == -1) {
			perror("Error on fstat");
			exit (EXIT_FAILURE);
		}
		fprintf(stderr,"Opened file: %d kb\n", (int) fs.st_size);
		map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);

		if (map == MAP_FAILED) {
			close(fd);
			perror("Error mmapping the file");
			exit (EXIT_FAILURE);
		}
		fprintf(stderr,"finished mmap\n", "");
	}
public:
	int count, *COUNTS;
	virtual bool getinput() = 0;
};

template<class T>
class MMapFILEParser {
};


template<>
class MMapFILEParser<DCS_FORMAT> : public MMapFILEParserBase {
private:
	float *num_offset;
	int *ir_offset, curr;
	void init() {
		fprintf(stderr, "start Init\n");
		int CP_SIZE;
		this->ir_offset = (int *) this->map;

		this->NNZ_SIZE = this->ir_offset[0];
		CP_SIZE = this->ir_offset[1];
		IR_SIZE = this->ir_offset[2];
		STRIDE = IR_SIZE;

		this->JC = (int*) malloc(this->NNZ_SIZE * sizeof(int));
		this->CP = (int*) malloc(CP_SIZE * sizeof(int));

		memcpy(this->JC, (this->ir_offset + 4), NNZ_SIZE * sizeof(int));
		memcpy(this->CP, (this->ir_offset + 4 + NNZ_SIZE),
				CP_SIZE * sizeof(int));
		COUNTS = (int*) malloc(sizeof(int) * (NNZ_SIZE + 1));
		int max = -1;
		for (int i = 0; i < NNZ_SIZE; i++) {
			COUNTS[i] = CP[i + 1] - CP[i];
			//fprintf(stderr,"%d ", COUNTS[i]);
			if (COUNTS[i] > max)
			max = COUNTS[i];
		}
		COUNTS[NNZ_SIZE] = max;

		/*fprintf(stderr,"Printing CONTENTS of header\n", "");
		 for (int j = 0; j < NNZ_SIZE; j++) {
		 fprintf(stderr,"%d ", JC[j]);
		 }
		 fprintf(stderr,"\n", "");
		 for (int j = 0; j < CP_SIZE; j++) {
		 fprintf(stderr,"%d ", CP[j]);
		 }
		 fprintf(stderr,"\n", "");
		 for (int j = 0; j < NNZ_SIZE+1; j++) {
		 fprintf(stderr,"%d ", COUNTS[j]);
		 }
		 fprintf(stderr,"\n", "");*/

		ir_offset += 4 + NNZ_SIZE + CP_SIZE;
		num_offset = (float*) (ir_offset + IR_SIZE);
		curr = 0;

	}
public:
	int *JC, *CP, *CURR_PTR, NNZ_SIZE,STRIDE, IR_SIZE;
	float *NUM_PTR;
	MMapFILEParser<DCS_FORMAT>(char * filename) :
	MMapFILEParserBase(filename) {
		fprintf(stderr, "called DCS\n");
		init();
		fprintf(stderr, "Finished INIT\n");

	}
	bool getinput() {
		if (JC[curr] != -1) {
			//fprintf(stderr,"getting input\n");
			int ir_begin = CP[curr];
			int ir_end = CP[curr + 1];
			count = ir_end - ir_begin;
			CURR_PTR = (ir_offset + ir_begin);
			NUM_PTR = (float*) (num_offset + ir_begin);
			JC[curr] = -1;
			curr = (curr + 1) % NNZ_SIZE;
			return true;
		} else {
			if (munmap(map, fs.st_size) == -1) {
				perror("Error un-mmapping the file");
			}
			close(this->fd);
			return false;
		}
	}
};

template<>
class MMapFILEParser<COO_FORMAT> : public MMapFILEParserBase {
private:
	int *OFFSET,curr, *NNZ_COUNTS;
	void init() {
		this->OFFSET = (int*) this->map;
		this->NNZ_SIZE= this->OFFSET[0];
		this->NNZ_COUNTS = (int*) malloc(this->NNZ_SIZE*sizeof(int));
		memcpy(this->NNZ_COUNTS, (this->OFFSET+1),NNZ_SIZE * sizeof(int));
		this->COUNTS = (int *) malloc((this->NNZ_SIZE+1)*sizeof(int));
		int max = -1;
		for (int i = 0 ; i < this->NNZ_SIZE; i++) {
			COUNTS[i] = NNZ_COUNTS[i+1] - NNZ_COUNTS[i];
			if (COUNTS[i]> max) {
				max = COUNTS[i];
			}
		}
		COUNTS[this->NNZ_SIZE] = max;
		OFFSET += 1 + this->NNZ_SIZE;
		STRIDE = NNZ_SIZE;

		 /** Get filepointer start for row index
		 * Get*/
		curr = 0;
	}
public:
	int NNZ_SIZE, STRIDE,*CURR_PTR;
	float *NUM_PTR;

	MMapFILEParser<COO_FORMAT>(char * filename) :
	MMapFILEParserBase(filename) {
		fprintf(stderr, "called COO\n");
		init();
	}
	bool getinput() {
		if (curr < this->NNZ_SIZE) {
			CURR_PTR = (OFFSET + NNZ_COUNTS[curr]);
			NUM_PTR = (float*) (CURR_PTR + (2*NNZ_COUNTS[curr]));
			curr++;
			return true;
		} else {
			return false;
		}
	}
};



void writetofile(char* filename, ENTRY_VEC entries) {
	ofstream file;
	file.open(filename);
	ENTRY_VEC::iterator it;
	stringstream ss;
	for (it = entries.begin(); it != entries.end(); it++) {
		file << it->i << "\t" << it->j << "\t" << it->weight << "\n";
	}
	file.close();
};

void writetofile(char* filename, Entry * entries, int size) {
	ofstream file;
	file.open(filename);
	for (int i = 0; i < size; i++) {
		file << entries[i].i << "\t" << entries[i].j << "\t"
				<< entries[i].weight << "\n";
	}
	file.close();
};
